#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <err.h>
#include <string.h>
#include <semaphore.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include "ra_flcontcent.h"
#include "rafcc_settings.h"

void release_pid_file();
void show_instruction();
int start_daemon();
int stop_daemon();
int status_daemon();
int monopolyze_pid_file();
int daemon_main();
long int get_daemon_pid(char *pid_file);
int send_status();

int pid_fd = -1;
int daemon_started = 0;

int main(int argc, char *argv[])
{
	if (argc != 2)
	{
		show_instruction();
		return EXIT_SUCCESS;
	};

	init_settings_byzero();	
	init_logfile(LOG_FILENAME, argc, argv);
	
	int ret = EXIT_SUCCESS;
	if (strcmp ("start", argv[1]) == 0)
		ret = start_daemon();
	else if (strcmp ("stop", argv[1]) == 0)
		ret = stop_daemon();
	else if (strcmp ("status", argv[1]) == 0)
		ret = status_daemon();
	else
	{
		show_instruction();
		ret = EXIT_SUCCESS;
	};

	// Если демон не стартовал, то освобождаю структуру логирования, в противном случае этим будет заниматься сам демон
	if (daemon_started == 0)
		free_logfile();
		
	return ret;
}

int start_daemon()
{
	int pid;
	int shm_id = -1;
	int *share_addr = NULL;

	shm_id = shmget (getpid(), sizeof(int), IPC_CREAT | 0666 );
	if (shm_id == -1)
	{
		printf("Error: Start Daemon failed (%s)\n", strerror(errno));
		return EXIT_FAILURE;
	};

	share_addr = shmat( shm_id, 0, 0);
	*share_addr = 0;

	sem_t *sem;
	sem = sem_open("/ra_fctl_sem", O_CREAT, 0777, 0);
	if (sem == SEM_FAILED)
	{
		printf("Error: Start Daemon failed: semaphore error: (%s)\n", strerror(errno));
		return EXIT_FAILURE;
	};
	sem_post(sem);
	sem_wait(sem);

	// создаем потомка
	pid = fork();

	if (pid == -1) // если не удалось запустить потомка
	{
		// выведем на экран ошибку и её описание
		printf("Error: Start Daemon failed (%s)\n", strerror(errno));
		return EXIT_FAILURE;
	} else if (!pid) // если это потомок
    {
		if ( monopolyze_pid_file() != 0)
		{
			*share_addr = DAEMON_STATUS_FAILED;
			sem_post(sem);
			_exit(-1);
		} else
		{
			//Читаю настроечный файл
			if (load_settings(CONF_FILENAME) != EXIT_SUCCESS)
			{
				*share_addr = DAEMON_STATUS_FAILED;
				sem_post(sem);
				_exit(EXIT_FAILURE);
			};
			// данный код уже выполняется в процессе потомка
			// разрешаем выставлять все биты прав на создаваемые файлы, 
			// иначе у нас могут быть проблемы с правами доступа
			umask(0);

			// создаём новый сеанс, чтобы не зависеть от родителя
			setsid();

			// переходим в корень диска, если мы этого не сделаем, то могут быть проблемы.
			// к примеру с размантированием дисков
			chdir("/");

			// закрываем дискрипторы ввода/вывода/ошибок, так как нам они больше не понадобятся
			close(STDIN_FILENO);
			close(STDOUT_FILENO);
			close(STDERR_FILENO);

			*share_addr = DAEMON_STATUS_OK;
			sem_post(sem);

			int ret = daemon_main();

			release_pid_file();
			clear_settings();
			free_logfile();

			_exit(ret);
		};
	} else // если это родитель
	{
		// Waiting semaphore for getting status
		sem_wait (sem);

		switch((int)*share_addr)
		{
			case DAEMON_STATUS_FAILED:
				printf("Start daemon failed\n");
				break;
			case DAEMON_STATUS_OK:
				printf("Start daemon started successfully\n");
				daemon_started = 1;
				break;
		};

		shmctl(shm_id, IPC_RMID, 0);
		shm_id = 0;
		sem_close(sem);

		// завершим процес, т.к. основную свою задачу (запуск демона) мы выполнили
		return EXIT_SUCCESS;
	};

	return EXIT_SUCCESS;
}

int daemon_main()
{
	sigset_t sigset;
	siginfo_t siginfo;
	
	// настраиваем сигналы которые будем обрабатывать
	sigemptyset(&sigset);

	// сигнал остановки процесса пользователем
	sigaddset(&sigset, SIGQUIT);
	// сигнал для остановки процесса пользователем с терминала
	sigaddset(&sigset, SIGINT);
	// сигнал запроса завершения процесса
	sigaddset(&sigset, SIGTERM);
	// сигнал посылаемый при изменении статуса дочернего процесса
	sigaddset(&sigset, SIGCHLD);
	// пользовательский сигнал который мы будем использовать для обновления конфига
	sigaddset(&sigset, SIGUSR1); 
	sigprocmask(SIG_BLOCK, &sigset, NULL);

	for(;;)
	{
		// ожидаем поступление сигнала
		sigwaitinfo(&sigset, &siginfo);
		
		// если пришел сигнал от потомка
		if (siginfo.si_signo == SIGCHLD)
		{
		} else if (siginfo.si_signo == SIGUSR1) // если пришел сигнал что необходимо прислать статус
		{
			send_status();
		} else // если пришел какой-либо другой ожидаемый сигнал
		{
			// Выхожу из главного цикла
			break;
		};
	};

	return 0;
}

long int get_daemon_pid(char *pid_file)
{
	int pid_fdstop = -1;
	char pid_buf[21];
	char filename[40];

	/* собственно, открываем файл */
	pid_fdstop = open(PID_FILENAME, O_RDONLY);
	if (pid_fdstop < 0) 
		return -1;
	
	int length = read(pid_fdstop, pid_buf, 20);
	close(pid_fdstop);

	if (length = 0)
		return -1;
	
	//Отрезаю конец файла
	char * pch;
	pch = (char*) memchr(pid_buf, '\n', strlen(pid_buf));
	if (pch != NULL)
		pch[0] = '\0';
	
	//PID процесса
	long int pid_daemon = strtol(pid_buf, NULL, 0);
	
	//Проверяю существование процесса
	strcpy(filename, "/proc/");
	strcat(filename, pid_buf);
		
	if (access(filename, 0) != 0)
		return -1;
	
	if (pid_file != NULL)
		strcpy(pid_file, filename);
	
	return pid_daemon;
}

int status_daemon()
{
	//PID процесса
	long int pid_daemon = get_daemon_pid(NULL);

	//Грохаю файл fifo, если он существует
	if (access(FIFO_FILENAME, 0) == 0)
		unlink(FIFO_FILENAME);
	
	if (pid_daemon < 0)
	{
		printf("Daemon not running\n");
		return -1;
	};

    int fd = -1;

    if (mkfifo(FIFO_FILENAME, 0777)) 
	{
		printf("Error creating FIFO: (%s)\n", strerror(errno));
        return -1;	
	};
	//printf("%d\n", pid_daemon);
	
	//Сообщаю демону что готов читать данные
	kill(pid_daemon, SIGUSR1);	
	
    if ( (fd = open(FIFO_FILENAME, O_RDONLY)) < 0 ) 
	{
		printf("Error opening FIFO: (%s)\n", strerror(errno));
		remove(FIFO_FILENAME);
        return -1;
    };
	
	char *buf = NULL;
	buf = malloc(FIFO_BUFSIZE + 1);
	
	int count_read = read(fd, buf, FIFO_BUFSIZE);
	buf[count_read] = 0;
	
	printf("%s", buf);
	
    close(fd);
    remove(FIFO_FILENAME);
	free(buf);
	buf = NULL;
	
	return 0;
}

int send_status()
{
    int fd = -1;
    char *buf = NULL;
	
	buf = malloc(FIFO_BUFSIZE + 1);
	memset(buf, 0, FIFO_BUFSIZE + 1);
	
    if ( (fd = open(FIFO_FILENAME, O_WRONLY)) < 0 )
        return -1;

	strcpy(buf, "Yehooooo!!!!!\n");

	write(fd, buf, strlen(buf));
	
    close(fd);

	free(buf);
	buf = NULL;
	
	return 0;
}

int stop_daemon()
{
	char filename[40];

	//PID процесса
	long int pid_daemon = get_daemon_pid(filename);

	if (pid_daemon < 0)
	{
		printf("Daemon not running\n");
		return -1;
	};
	
	//Прибиваю процесс
	kill(pid_daemon, SIGQUIT);
	
	//И проверяю результат
	int i = 0;
	for (i = 0; i < 7; i++)
	{
		if (access(filename, 0) != 0)
			i = 100;
		else
			sleep(0.5);
	};
	
	if (i < 100)
		printf("Daemon status unknown, but he trying to stop.\n");
	else
		printf("Daemon stopped.\n");
	
	return 0;
}

int monopolyze_pid_file()
{
	int ok = 0;
	char *pid_buf = NULL;
	int pid_len;

	do 
	{
		/* собственно, открываем файл */
		pid_fd = open(PID_FILENAME, O_CREAT | O_WRONLY);
		if (pid_fd < 0) 
		{
			warn("can't open pid file: %s", PID_FILENAME);
			break;
		};

		/* пытаемся его заблокировать - собственно проверка наличия копий процесса */
		if (flock(pid_fd, LOCK_EX | LOCK_NB) < 0)
		{
			pid_fd = -1;
			if (errno == EWOULDBLOCK)
				warnx("Daemon already running!");
			else
				warn("flock() failed: %s", PID_FILENAME);
			break;
		};

		/* файл заблокирован - всё хорошо, надо записать туда наш pid */

		//pid_len = sprintf(pid_buf, "%ld\n", getpid());
		pid_len = asprintf(&pid_buf, "%ld\n", (long long)getpid());
		if (!pid_buf) 
		{
			warn("asprintf() failed");
			break;
		};

		/* теперь надо обрезать pid-файл до нужного размера, иначе после записи там может остаться лишнее */
		if (ftruncate(pid_fd, (off_t)pid_len) < 0) 
		{
			warn("ftruncate() failed");
			break;
		};

		/* собственно, записываем pid. ввод/вывод блокирующий, поэтому достаточно одного вызова */
		if (write(pid_fd, (void *)pid_buf, (size_t)pid_len) < (ssize_t)pid_len) 
		{
			warn("write() failed");
			break;
		};

		ok = 1;
	} while (0);

	/* корректно завершаемся при ошибках */
	if (!ok) 
	{
		/* закрываем и удаляем pid-файл, если мы его таки открыли */
		if (pid_fd >= 0)
		{
			unlink(PID_FILENAME);
			close(pid_fd);
			pid_fd = -1;
		};
	};

	/* освобождаем память */
	if (pid_buf)
		free(pid_buf);

	return ok ? 0 : -1;
}

void show_instruction()
{
	printf("Use parametrs:\n");
	printf("\tstart - start daemon.\n");
	printf("\tstop - stop daemon.\n");
	printf("\tstatus - show current daemon status.\n");
}


void release_pid_file()
{
	/* закрываем и удаляем pid-файл */
	if (pid_fd >= 0) 
	{
		unlink(PID_FILENAME);
		close(pid_fd);
		pid_fd = -1;
	};
}

/*
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stddef.h>
#include <stdlib.h>

int main()
{
    int sock, listener;
    struct sockaddr_in addr;
    char buf[1024];
    int bytes_read;

    listener = socket(AF_INET, SOCK_STREAM, 0);
    if(listener < 0)
    {
        perror("socket");
        exit(1);
    }
    
    addr.sin_family = AF_INET;
    addr.sin_port = htons(3425);
    addr.sin_addr.s_addr = INADDR_ANY;
    if(bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind");
        exit(2);
    }

    listen(listener, 1);
    
    while(1)
    {
        sock = accept(listener, NULL, NULL);
        if(sock < 0)
        {
            perror("accept");
            exit(3);
        }
        
        switch(fork())
        {
        case -1:
            perror("fork");
            break;
            
        case 0:
            close(listener);
            while(1)
            {
				send(sock, "REWA", 4, 0);
                bytes_read = recv(sock, buf, 1024, 0);
                if(bytes_read <= 0) break;
                send(sock, buf, bytes_read, 0);
            }

            close(sock);
            _exit(0);
            
        default:
            close(sock);
        }
    }
    
    close(listener);

    return 0;
}
*/